/* This file is part of FireEscape.
 *
 * FireEscape is the intellectual work of Phillip Cheng & Allen Park.
 * All future releases or modifications of this program must include this
 * attribuation as according to the Creative Commons BY-SA license.
 *
 * FireEscape is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * FireEscape is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FireEscape.  If not, see <http://www.gnu.org/licenses/>.
 *
 * All modifications to this program should include this original statement.
 *
 * If you modify, distribute, or use the program , please contact
 * fireescape@kloudvine.com
 * http://code.google.com/p/fireescape/
 *
 * If you are interested in using FireEscape for your structure, please contact us
 * for support.
 *
 * If you do use FireEscape, please consider donating to the project.
 */
package fireescape;

import java.awt.geom.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.logging.*;

/**
 * Represents doorways and exits
 * @author chengP
 */
public class Exit {

    private Line2D.Double line;
    private Line2D.Double drawnLine;
    private RoomShape rect;
    private RoomShape drawnRect;
    private double length;
    private double angle;
    private int room1ID;
    private int room2ID;
    private int exitID;
    //private ArrayList<Integer> room1Approaching;
    //private ArrayList<Integer> room2Approaching;
    private int floorNum;
    private ArrayList<Integer> botIDsMovingThrough;

    /**
     * Default constructor
     */
    public Exit() {
        this(0, 0, 0, 0, -1, -1);
    }

    /**
     * Constructs the Exit
     */
    public Exit(double x1, double y1, double x2, double y2, int floorNum, int exitID) {
        line = new Line2D.Double(x1, y1, x2, y2);
        drawnLine = new Line2D.Double(FireEscapeGUI.scale * line.getX1(), FireEscapeGUI.scale * line.getY1(),
                FireEscapeGUI.scale * line.getX2(), FireEscapeGUI.scale * line.getY2());
        length = FireEscapeGUI.distance(x1, y1, x2, y2);
        angle = BotAI.calculateAngle(x1, y1, x2, y2);
        this.floorNum = floorNum;
        this.exitID = exitID;
        room1ID = -1;
        room2ID = -1;
        botIDsMovingThrough = new ArrayList<Integer>();
        /*if (!(this instanceof Staircase)) {     //shouldn't need it - debugging
        room1Approaching = new ArrayList<Integer>();
        room2Approaching = new ArrayList<Integer>();
        }*/
        generateRect();
    }

    public void generateRect() {
        ArrayList<Point2D.Double> vertices = new ArrayList<Point2D.Double>();
        double halfWidth = 2 * BotAI.botRadius + BotAI.defaultDistance;
        /*vertices.add(new Point2D.Double(line.getX1() - Math.sin(angle) * halfWidth - Math.cos(angle) * halfWidth,
        line.getY1() + Math.cos(angle) * halfWidth - Math.sin(angle) * halfWidth));
        vertices.add(new Point2D.Double(line.getX1() + Math.sin(angle) * halfWidth - Math.cos(angle) * halfWidth,
        line.getY1() - Math.cos(angle) * halfWidth - Math.sin(angle) * halfWidth));
        vertices.add(new Point2D.Double(line.getX2() + Math.sin(angle) * halfWidth + Math.cos(angle) * halfWidth,
        line.getY2() - Math.cos(angle) * halfWidth + Math.sin(angle) * halfWidth));
        vertices.add(new Point2D.Double(line.getX2() - Math.sin(angle) * halfWidth + Math.cos(angle) * halfWidth,
        line.getY2() + Math.cos(angle) * halfWidth + Math.sin(angle) * halfWidth));*/
        vertices.add(new Point2D.Double(line.getX1() - Math.sin(angle) * halfWidth,
                line.getY1() + Math.cos(angle) * halfWidth));
        vertices.add(new Point2D.Double(line.getX1() + Math.sin(angle) * halfWidth,
                line.getY1() - Math.cos(angle) * halfWidth));
        vertices.add(new Point2D.Double(line.getX2() + Math.sin(angle) * halfWidth,
                line.getY2() - Math.cos(angle) * halfWidth));
        vertices.add(new Point2D.Double(line.getX2() - Math.sin(angle) * halfWidth,
                line.getY2() + Math.cos(angle) * halfWidth));
        rect = new RoomShape(vertices);
        if (Map.drawExitRects) {
            drawnRect = new RoomShape(FireEscapeGUI.scale(vertices));
        }
    }

    public void overrideRect(RoomShape rect) {
        this.rect = rect;
    }

    public void overrideDrawnRect(RoomShape drawnRect) {
        this.drawnRect = drawnRect;
    }

    /**
     * Returns the exit ID
     * @return The exit ID
     */
    public int getExitID() {
        return exitID;
    }

    /**
     * Draws the exit
     * @param g The Graphics2D object used for drawing
     */
    public void paint(Graphics2D g) {
        g.setColor(Color.white);
        g.draw(drawnLine);
        if (Map.drawExitRects) {
            g.setColor(Color.green);
            drawnRect.draw(g);
        }
    }

    /**
     * Returns the other room this is connected to
     * @param Room The first room
     * @return The other room
     */
    public int connectedToID(int roomID) {
        if (roomID == room1ID) {
            return room2ID;
        } else if (roomID == room2ID) {
            return room1ID;
        }
        return -1;
    }

    /**
     * Tries to set a Room. Returns true if successful, false if both Rooms are already full. Use overrideroom1(Room) or overrideroom2(Room) if both Rooms are full.
     * @param Room The new Room.
     * @return True if successful, false if both Rooms are already full.
     */
    public boolean setRoom(int roomID) {
        if (room1ID == -1) {
            room1ID = roomID;
        } else if (room2ID == -1) {
            room2ID = roomID;
        } else {
            return false;
        }
        return true;
    }

    /**
     * Overrides room1 with a new Room.
     * @param Room The new Room.
     */
    public void overrideRoom1(int roomID) {
        room1ID = roomID;
    }

    /**
     * Overrides room2 with a new Room.
     * @param Room The new Room.
     */
    public void overrideRoom2(int roomID) {
        room2ID = roomID;
    }

    /**
     * Returns the scaled line to be drawn
     * @return The scaled line to be drawn
     */
    public Line2D.Double getDrawnLine() {
        return drawnLine;
    }

    /**
     * Returns the rectangle surrounding the exit
     * @return The rectangle surrounding the exit
     */
    public RoomShape getRect() {
        return rect;

        /*double halfWidth = BotAI.botRadius + BotAI.defaultDistance / 2;
        if (getX1() == getX2()) {
        return new Rectangle2D.Double(getX1() - halfWidth, Math.min(getY1(), getY2()),
        halfWidth * 2, Math.abs(getY2() - getY1()));
        } else if (getY1() == getY2()) {
        return new Rectangle2D.Double(Math.min(getX1(), getX2()), getY1() - halfWidth,
        Math.abs(getX2() - getX1()), halfWidth * 2);
        }
        System.out.println("MEHHH");
        return null;
        //return new Rectangle2D.Double(Math.min(line.getX1(), line.getX2()) - 5, Math.min(line.getY1(), line.getY2()) - 5, Math.abs(line.getX2() - line.getX1()) + 10, Math.abs(line.getY2() - line.getY1()) + 10);*/
    }

    /**
     * Returns the rectangle surrounding the exit in drawn scale
     * @return The rectangle surrounding the exit in drawn scale
     */
    public RoomShape getDrawnRect() {
        return drawnRect;
    }

    /**
     * Returns if the x coordinate is within the x range
     * @param x The x coordinate
     * @return True if the x coordinate is within the range, false if not
     */
    public boolean isInXRange(double x) {
        return (x >= getX1() && x <= getX2()) ||
                (x >= getX2() && x <= getX1());
    }

    /**
     * Returns if the y coordinate is within the x range
     * @param y The y coordinate
     * @return True if the y coordinate is within the range, false if not
     */
    public boolean isInYRange(double y) {
        return (y >= getY1() && y <= getY2()) ||
                (y >= getY2() && y <= getY1());
    }

    /**
     * Returns which room the room ID belongs to
     * @param roomID The room ID
     * @return Which room the room ID belongs to
     */
    public int whichRoom(int roomID) {
        if (roomID == room1ID) {
            return 1;
        } else if (roomID == room2ID) {
            return 2;
        }
        Logger.getLogger("FireEscapeLog").log(Level.SEVERE, "Method should always return 1 or 2.");
        return -1;
    }

    /**
     * Add the bot to the moving through list
     * @param b The bot ID to be added
     */
    public void addBotMovingThrough(Bot b) {
        addBotIDMovingThrough(b.getID());
    }

    /**
     * Add the bot ID to the moving through list
     * @param botID The bot ID to be added
     */
    public void addBotIDMovingThrough(int botID) {
        botIDsMovingThrough.add(new Integer(botID));
    }

    /**
     * Remove the bot from the moving through list
     * @param b The bot to be removed
     */
    public void removeBotMovingThrough(Bot b) {
        removeBotIDMovingThrough(b.getID());
    }

    /**
     * Remove the bot ID from the moving through list
     * @param botID The bot ID to be removed
     */
    public void removeBotIDMovingThrough(int botID) {
        botIDsMovingThrough.remove(new Integer(botID));
    }

    public int getBotIDMovingThrough(int i) {
        return botIDsMovingThrough.get(i);
    }

    /**
     * Returns a direction perpendicular to the next exit
     * @return A direction perpendicular to the next exit between 0 and pi
     */
    public double perpendicularToExit() {
        if (getX1() == getX2()) {
            return 0;
        } else if (getY1() == getY2()) {
            return Math.PI / 2;
        }
        double slope = (getY2() - getY1()) / (getX2() - getX1());
        double angle = Math.atan(-1 / slope);
        if (angle < 0) {
            angle += Math.PI;
        }
        return angle;
        /*if (waypoint.getX1() == waypoint.getX2()) {
        if (room.getX() == waypoint.getX1()) {
        return Math.PI;
        } else if (room.getX() + room.getWidth() == waypoint.getX1()) {
        return 0;
        }
        } else if (waypoint.getY1() == waypoint.getY2()) {
        if (room.getY() == waypoint.getY1()) {
        return 3 * Math.PI / 2;
        } else if (room.getY() + room.getHeight() == waypoint.getY1()) {
        return Math.PI / 2;
        }
        }
        System.out.println("xFail");
        return 1.0 / 0;                 //throwing error on purpose
        //clarifying allen's wierd error so I don't have to do it again. This aint going to happen.*/
    }

    /*public double distanceFromRectangle(double x, double y) {
    boolean isInXRange = isInXRange(x);
    boolean isInYRange = isInYRange(y);
    if (isInXRange && isInYRange) {
    return 0;
    } else if (isInXRange) {
    return Math.min(Math.abs(y - getY1()), Math.abs(y - getY2()));
    } else if (isInYRange) {
    return Math.min(Math.abs(x - getX1()), Math.abs(x - getX2()));
    } else {
    return Math.min(BotAI.distance(x, y, getX1(), getY1()), BotAI.distance(x, y, getX2(), getY2()));
    }
    }*/

    /*public void setApproaching(Bot bot) {
    Room room = Map.getRoom(bot.getRoomID());
    if (room.equals(room1)) {
    room1Approaching.add(bot.getBotNum());
    } else if (room.equals(room2)) {
    room2Approaching.add(bot.getBotNum());
    }
    }

    public void removeApproaching(Bot bot) {
    Room room = Map.getRoom(bot.getRoomID());
    if (room.equals(room1)) {
    room1Approaching.remove(new Integer(bot.getBotNum()));
    } else if (room.equals(room2)) {
    room2Approaching.remove(new Integer(bot.getBotNum()));
    }
    }

    public int getNumBotsApproaching(int whichRoom) {
    if (whichRoom == 1) {
    return room1Approaching.size();
    } else if (whichRoom == 2) {
    return room2Approaching.size();
    }
    System.out.println("phailyeure2");
    return -1;
    }

    public Bot getBotApproaching(int index, int whichRoom) {
    if (whichRoom == 1) {
    return BotAI.getBot(room1Approaching.get(index));
    } else if (whichRoom == 2) {
    return BotAI.getBot(room2Approaching.get(index));
    }
    return null;
    }
    
    public void resetApproaching() {
    room1Approaching = new ArrayList<Integer>();
    room2Approaching = new ArrayList<Integer>();
    }*/
    /**
     * Returns the x coordinate of the first corner
     * @return The x coordinate of the first corner
     */
    public double getX1() {
        return line.getX1();
    }

    /**
     * Returns the x coordinate of the second corner
     * @return The x coordinate of the second corner
     */
    public double getX2() {
        return line.getX2();
    }

    /**
     * Returns the y coordinate of the first corner
     * @return The y coordinate of the first corner
     */
    public double getY1() {
        return line.getY1();
    }

    /**
     * Returns the y coordinate of the second corner
     * @return The y coordinate of the second corner
     */
    public double getY2() {
        return line.getY2();
    }

    /**
     * Returns the line representing the exit
     * @return The line representing the exit
     */
    public Line2D.Double getLine() {
        return line;
    }

    /**
     * Returns the center x of the exit
     * @return The center x of the exit
     */
    public double getCenterX() {
        return (getX1() + getX2()) / 2;
    }

    /**
     * Returns the center y of the exit
     * @return The center y of the exit
     */
    public double getCenterY() {
        return (getY1() + getY2()) / 2;
    }

    /**
     * Returns the length of the line.
     * @return The length of the line.
     */
    public double getLength() {
        return length;
    }

    /**
     * Returns the angle of the line.
     * @return The angle of the line.
     */
    public double getAngle() {
        return angle;
    }

    /**
     * Returns a random x coordinate on the exit
     * @return A random x coordinate on the exit
     */
    public double getRandomX() {
        double random = Math.random();
        return getX1() * random + getX2() * (1 - random);
    }

    /**
     * Returns a random y coordinate on the exit
     * @return A random y coordinate on the exit
     */
    public double getRandomY() {
        double random = Math.random();
        return getY1() * random + getY2() * (1 - random);
    }

    /**
     * Returns the room ID of the first room
     * @return The room ID of the first room
     */
    public int getRoom1ID() {
        return room1ID;
    }

    /**
     * Returns the room ID of the second room
     * @return The room ID of the second room
     */
    public int getRoom2ID() {
        return room2ID;
    }

    /**
     * Returns the floor number that this exit is on
     * @return The floor number that this exit is on
     */
    public int getFloorNum() {
        return floorNum;
    }

    /**
     * Returns if the exit connects to the outside
     * @return True if the exit connects to the outside, false if not
     */
    public boolean connectedToOutside() {
        return room1ID == 0 || room2ID == 0;
    }

    /**
     * Returns the distance between the centers of the exits
     * @param exit The second exit
€     * @return The distance between the centers of the exits
     */
    public double centerDistance(Exit exit) {
        return FireEscapeGUI.distance(getCenterX(), getCenterY(), exit.getCenterX(), exit.getCenterY());
    }

    /**
     * Returns if the exit equals the other object
     * @param other The other object
     * @return True if the exit equals the other object, false if not
     */
    @Override
    public boolean equals(Object other) {
        if (!(other instanceof Exit)) {
            return false;
        }
        Exit exit = (Exit) other;
        return this.exitID == exit.exitID;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 13 * hash + this.room1ID;
        hash = 13 * hash + this.room2ID;
        hash = 13 * hash + this.exitID;
        hash = 13 * hash + this.floorNum;
        return hash;
    }

    /**
     * Returns a description of the exit object
     * @return The coordinates of the exit
     */
    @Override
    public String toString() {
        return "X1:" + this.getX1() + " Y1:" + this.getY1() + " X2:" + this.getX2() + " Y2:" + this.getY2();
    }
}
